Eléments de programmation

Pour mener à bien un calcul algorithmique le nombre d'éléments de langage n'est pas très important et peut se résumer aux 3 syntaxes suivantes

  • Le choix conditionnel if-elseif-else-end.
  • La boucle for-end.
  • La boucle while-end

if ... elseif ... else ... end

Un choix simple si le test est vrai (k==1) alors le bloc d'instruction est évalué


In [1]:
k=1;
if k==1
    println("k=1")
end


k=1

Le else permet de donner un résultat par défaut...


In [2]:
k=1;
if k!=1
    println("k<>1")
else
    println("k=1")
end


k=1

Une succession de elseif permet de choisir parmi plusieurs critères, dans la succession des blocs de if et elseif le premier qui est "vrai" est évaluer et l'instruction s'arrète.


In [3]:
k=2;
if k==1
    println("k=1")
elseif k>1
    println("k>1")
else 
    println("k<1")
end


k>1

La boucle for

Elle peut se définir à l'aide d'itérateurs ou de tableaux de valeurs les syntaxes "=" ou "in" sont équivalentes


In [4]:
for i=1:10
    println(i)
end


1
2
3
4
5
6
7
8
9
10

In [5]:
for i in ["un" 2 im]
    println(typeof(i))
end


String
Int64
Complex{Bool}

La commande break permet de sortir d'une boucle à tout moment


In [6]:
for i = 1:1000
    println(i)
    if i >= 5
       break
    end
end


1
2
3
4
5

La commande continue permet elle de courtcircuiter la boucle en court et de passer à la valeur suivante


In [7]:
for i = 1:10
    if i % 3 != 0 # i modulo 3 different de 0
        continue
    end
    println(i)
end


3
6
9

La boucle while

Tant que le test est "vrai" le bloc est évalué, le test se faisant en entrée de bloc


In [8]:
k=0;
while k<10
    k+=1  # k=k+1
    println(k)
end


1
2
3
4
5
6
7
8
9
10

De même que la boucle for les commandes break et continue sont valables ...


In [9]:
k=0;
while k<1000
    k+=1  # k=k+1
    if k % 5 != 0 # k modulo 5 diffèrent de 0
        continue # retour en début de boucle avec de nouveau un test sur k
    end
    println(k)
    if k>30
        break
    end
end


5
10
15
20
25
30
35

Un peu d'optimisation

JULIA est dit un langage performant, regardons rapidement quelques exemples à faire ou ne pas faire.

Exemple de préallocation et utilisation de push!


In [34]:
start = time()
A=zeros(0);
for i=1:1000000
    A=[A;i];   # a chaque itération on change la taille de A
end
elapsed = time() - start


InterruptException:

Stacktrace:
 [1] __cat(::Array{Float64,1}, ::Tuple{Int64}, ::Tuple{Bool}, ::Array{Float64,1}, ::Vararg{Any,N} where N) at ./abstractarray.jl:1402
 [2] vcat(::Array{Float64,1}, ::Int64) at ./abstractarray.jl:1392
 [3] top-level scope at ./In[34]:4

In [32]:
typeof(A)


Out[32]:
Array{Float64,1}

In [33]:
A


Out[33]:
10000-element Array{Float64,1}:
     1.0
     2.0
     3.0
     4.0
     5.0
     6.0
     7.0
     8.0
     9.0
    10.0
    11.0
    12.0
    13.0
     ⋮  
  9989.0
  9990.0
  9991.0
  9992.0
  9993.0
  9994.0
  9995.0
  9996.0
  9997.0
  9998.0
  9999.0
 10000.0

In [20]:
start = time()
A=zeros(0);
for i=1:1000000
    push!(A,i);   # a chaque itération on change la taille de A
end
elapsed = time() - start


Out[20]:
0.1355280876159668

In [16]:
A


Out[16]:
10000-element Array{Float64,1}:
     1.0
     2.0
     3.0
     4.0
     5.0
     6.0
     7.0
     8.0
     9.0
    10.0
    11.0
    12.0
    13.0
     ⋮  
  9989.0
  9990.0
  9991.0
  9992.0
  9993.0
  9994.0
  9995.0
  9996.0
  9997.0
  9998.0
  9999.0
 10000.0

In [24]:
start = time()
A=zeros(Int64,1000000)
for i=1:1000000
    A[i]=i;
end
elapsed = time() - start


Out[24]:
0.06244397163391113

In [26]:
start = time()
A=[i for i=1:1000000]
elapsed = time() - start


Out[26]:
0.029053926467895508

In [27]:
typeof(A)


Out[27]:
Array{Int64,1}

Cet exemple montre le coût prohibitif d'une réallocation dynamique qui impose une recopie totale de A à chaque itération.

Exemple de vectorisation

Regardons la vectorisation sous JULIA à l'aide de la construction d'une matrice de Vandermond


In [35]:
start = time()
n=3000;
x=range(0,stop=1,length=n);
V=zeros(n,n);
for i=1:n
    V[:,i]=x.^(i-1) # calcul vectorisé
end
elapsed = time() - start


Out[35]:
0.813478946685791

In [42]:
A=rand(3,3)
A[:,1].=1
A


Out[42]:
3×3 Array{Float64,2}:
 1.0  0.0486277  0.0521712
 1.0  0.916497   0.832676 
 1.0  0.251161   0.257908 

In [43]:
start = time()
n=3000;
x=range(0,stop=1,length=n);
X=zeros(n,n);
for i=1:n
    for j=1:n
        X[i,j]=x[i]^(j-1) # calcul dévectorisé
    end
end
elapsed = time() - start


Out[43]:
2.014601945877075

In [ ]:
typeof(X)

In [45]:
start = time()
n=3000;
x=range(0,stop=1,length=n);
W=[x[i]^(j-1) for i=1:n, j=1:n];
elapsed = time() - start


Out[45]:
0.9836759567260742

In [ ]:
typeof(W)

In [46]:
function Vander(n)
    x=range(0,stop=1,length=n);
    V=zeros(n,n);
    for i=1:n
        #V[:,i]=x.^(i-1)
        for j=1:n
            V[i,j]=x[i]^(j-1) # calcul dévectorisé
        end
    end
    return V
end
start = time(); Z=Vander(3000); elapsed = time() - start


Out[46]:
0.21810603141784668

In [47]:
start = time(); Z=Vander(3000); elapsed = time() - start


Out[47]:
0.1992809772491455

In [48]:
start = time(); Z=Vander(3000); elapsed = time() - start


Out[48]:
0.2188708782196045

In [49]:
function Vander2(n)
    x=range(0,stop=1,length=n);
    [x[i]^(j-1) for i=1:n, j=1:n];
end
start = time(); Z2=Vander2(3000); elapsed = time() - start


Out[49]:
0.37661004066467285

In [50]:
start = time(); Z2=Vander2(3000); elapsed = time() - start  
typeof(Z2)


Out[50]:
Array{Float64,2}

La conclusion n'est pas toujours évidente a priori... Néanmoins on peut voir que le fait de mettre le code dans une fonction impose une optimisation à la compilation et une contextualisation du type et que l'écriture explicite des boucles donne un meilleur résultat.


In [ ]: